#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <jsoncpp/json/json.h>
#include "index.hpp"
#include "Util.hpp"
#include "log.hpp"

namespace ns_search
{
    // 用于保存多个关键字对应同一个文档的情况
    struct InvertedElems
    {
        uint64_t doc_id;
        int weight;
        std::vector<std::string> words;

        InvertedElems()
        :doc_id(-1)
        ,weight(0)
        ,words(1, "")
        {}
    };

    // 搜索类
    class Searcher
    {
    public:
        Searcher() = default;
        ~Searcher() = default;

        void InitSearcher(const std::string &text_path)
        {
            // 1. 创建Index单例
            pindex = ns_index::Index::GetInstance();
            // std::cout << "创建Index单例成功..." << std::endl;
            LOG(NORMAL, "创建Index单例成功...");
            // 2. 创建Index索引
            pindex->BuildIndex(text_path);
            // std::cout << "创建索引成功..." << std::endl;
            LOG(NORMAL, "创建索引成功...");
        }

        // target：要查找的字符串
        // output：返回查找结果(json字符串)
        void search(const std::string &target, std::string *output)
        {
            // 1. 对target进行jieba分词
            std::vector<std::string> tokens;
            // ns_util::JiebaUtil::CutString(target, &tokens);
            ns_util::JiebaUtil::GetInstance()->CutString(target, &tokens);

            // 2. 对每个分词进行查找，找到其倒排拉链，再将拉链内容保存
            // ns_index::InvertedList elemlist; // 保存所有符合条件的InvertedElem
            std::vector<InvertedElems> elemlist; // 保存所有符合条件的InvertedElems 其中words对应多个关键词，用于最后权值排序
            std::unordered_map<uint64_t, InvertedElems> elems_map; // 一个id对应唯一一个
            // 依次查找每个分词对应的拉链
            for (std::string str : tokens)
            {
                // 对于输入都转换为小写字母进行查询
                boost::to_lower(str);
                ns_index::InvertedList *plist = pindex->GetInvertedList(str);
                if (plist == nullptr)
                {
                    continue;
                }
                // 小bug：当会出现多个分词对应一个文档的情况，现在这种做法会直接将两个分词拆开各自插入elemlist中的情况TODO
                // elemlist.insert(elemlist.end(), plist->begin(), plist->end());
                for(auto& elem : *plist)
                {
                    auto& elems = elems_map[elem.doc_id]; // 插入或者查找对应的InvertedElems
                    elems.doc_id = elem.doc_id;
                    elems.weight += elem.weight;
                    // elems.words.push_back(elem.word);
                    // 这里选择最长的关键字
                    elems.words[0] = elems.words[0].size() > elem.word.size() ? elems.words[0] : elem.word;
                }
             }

            // 将去重后的InvertedElems插入elemlist
            for(auto& elems : elems_map)
            {
                elemlist.push_back(std::move(elems.second));
            }

            // 3. 对获取到的InvertedElems按照权值降序排列
            std::sort(elemlist.begin(), elemlist.end(), [](const InvertedElems &e1, const InvertedElems &e2)
                      { return e1.weight > e2.weight; });
            
            // 4. 构建json字符串
            Json::Value root;
            // Json::StyledWriter writer;
            Json::FastWriter writer;

            for (auto &elem : elemlist)
            {
                Json::Value elem_value;
                // 获取正排
                ns_index::DocInfo *pdoc = pindex->GetForwardIndex(elem.doc_id);
                if (pdoc == nullptr)
                {
                    continue;
                }
                // 关键词选择策略：1. 关键词权值最高（不用调整数据结构，可能是暂停词） 2. 关键词最长（但是也不用调整数据结构） 
                // 3. 现在的策略：保存全部关键字，优化性强

                elem_value["title"] = pdoc->title;
                elem_value["desc"] = GetDesc(pdoc->content, elem.words[0]);
                elem_value["url"] = pdoc->url;
                // for debug
                // elem_value["id"] = (int)pdoc->doc_id;
                // elem_value["weight"] = elem.weight;

                root.append(elem_value);
            }

            // 没有查找到的直接返回未查找到和首页url
            if(elemlist.size() == 0)
            {   
                Json::Value elem_value;
                elem_value["title"] = "未能查找到对应内容";
                elem_value["desc"] = "未能查找到对应内容，点击标题跳转到boost文档首页...";
                elem_value["url"] = "https://www.boost.org/doc/libs/1_80_0/";

                root.append(elem_value);
            }

            *output = writer.write(root);
        }

    private:
        std::string GetDesc(const std::string &content, const std::string word)
        {
            // 查找content中word首次出现的位置
            // 并截取word前50字节（不足50字节则从头开始），后100字节（不足100字节则到结尾结束）
            // 注意：我们是在content中查找word，word我们统一转换为了小写，但是content中大小写不确定，所以得依靠algorithm中search函数
            auto iter = std::search(content.begin(), content.end(), word.begin(), word.end(), [](int a, int b)
                                    { return (std::tolower(a) == std::tolower(b)); });

            if (iter == content.end())
            {
                return "None1";
            }
            // distance函数确定迭代器间距离
            size_t pos = std::distance(content.begin(), iter);
            size_t before_step = 50;
            size_t after_step = 100;
            std::string ret;

            // 判断距离问题 -- 注意：size_t尽量不能进行减法
            size_t start = pos > before_step ? pos - before_step : 0;
            if (start != 0)
            {
                ret += "...";
            }
            size_t end = pos + after_step < content.size() - 1 ? pos + after_step : content.size() - 1;

            if (start > end)
            {
                return "None2";
            }

            ret += content.substr(start, end - start);
            ret += "...";
            return ret;
        }

    private:
        ns_index::Index *pindex; // 用于查询的索引
    };
}